package com.teaclipse.model;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;

public class TeaQuadGenerator {

    /**
     * Counts the current temp being used
     */
    private static int tempCounter = 0;

    /**
     * this is a static reference if we have already made a Tea parser object
     * because the Tea parser object is created as static
     */
    private  static boolean teaParserInitialized = false;

    /**
      * a list to store quads
     */
    private QuadList quadList = new QuadList();

    /**
     * Default constructor
     */
    public TeaQuadGenerator(String fileName) {

        try
        {
            /* reference to the tea parser */
            Tea parser = null;

            /* make sure we are not creating the Tea static parser twice or else
               it will through an error in the constructor - we have to reinitialize
               the static parser  */
            if(!teaParserInitialized)
            {
                parser = new Tea(new FileInputStream(fileName));
                teaParserInitialized = true;
            }
            else
            {
                parser.ReInit(new FileInputStream(fileName));
            }
            /* parse the inputted program */
            SimpleNode root = parser.program();

            /* get a list of the quads*/
            ArrayList quadList = generate(root);

            /* print out the quad list */
            System.out.println(quadList);

            /* create an interpreter object and intepret the quads*/
            (new TeaInterpreter()).interpret(quadList);
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }

    /**
     * Generates quads starting from the given node
     * @param root SimpleNode
     * @return ArrayList
     */
    public ArrayList generate(SimpleNode root)
    {
        tempCounter = 0;
        quadList = new QuadList();
        generateQuad(root);
        return quadList;
    }

    /**
     * Real method to generate the quads
     * @param theNode SimpleNode
     * @return Object
     */
    protected Object generateQuad(SimpleNode theNode)
    {
        /* get the node id */
        int nodeId = theNode.id;
        Object returnValue = null;

        /* decide what to do based off of the node id */
        switch(nodeId)
        {
        case TeaTreeConstants.JJTPROGRAM:
        case TeaTreeConstants.JJTMETHOD_DECLARATION:
            returnValue = generateQuad((SimpleNode) theNode.children[0]);
            break;
        case TeaTreeConstants.JJTSTATEMENT_BLOCK:
            returnValue = generateStatementBlockQuad(theNode);
            break;
        case TeaTreeConstants.JJTDECLARATIVE_STATEMENT:
            returnValue = generateDeclarativeQuad(theNode);
            break;

        case TeaTreeConstants.JJTASSIGNMENT_STATEMENT:
            returnValue = generateAssignmentQuad(theNode);
            break;
        case TeaTreeConstants.JJTIF_STATEMENT:
            returnValue = generateIfStatementQuad(theNode);
            break;
        case TeaTreeConstants.JJTDO_WHILE_STATEMENT:
            returnValue = generateDoWhileStatementQuad(theNode);
            break;
        case TeaTreeConstants.JJTWHILE_STATEMENT:
            returnValue = generateWhileStatementQuad(theNode);
            break;
        case TeaTreeConstants.JJTFOR_STATEMENT:
            returnValue = generateForStatementQuad(theNode);
            break;
        case TeaTreeConstants.JJTNEGATE:
        case TeaTreeConstants.JJTPOSITIVE:
        case TeaTreeConstants.JJTADD:
        case TeaTreeConstants.JJTSUBTRACT:
        case TeaTreeConstants.JJTMULT:
        case TeaTreeConstants.JJTDIV:
        case TeaTreeConstants.JJTMOD:
        case TeaTreeConstants.JJTAND:
        case TeaTreeConstants.JJTOR:
        case TeaTreeConstants.JJTNOT_EQUAL_TO:
        case TeaTreeConstants.JJTEQUAL_TO:
        case TeaTreeConstants.JJTLESS_THAN:
        case TeaTreeConstants.JJTLESS_THAN_EQUAL_TO:
        case TeaTreeConstants.JJTGREATER_THAN:
        case TeaTreeConstants.JJTGREATER_THAN_EQUAL_TO:
            returnValue = generateExpressionQuad(theNode);
            break;
        case TeaTreeConstants.JJTVALUE:
            returnValue = theNode.firstValue;
            break;
        default:
           break;
        }
        return returnValue;
    }

    /**
     * Generates quads for the 'for' statement
     * @param theNode SimpleNode
     * @return Object
     */
    private Object generateForStatementQuad(SimpleNode theNode)
    {
        /* this means that there are all parts of the for statement*/
       if(theNode.children.length == 4)
       {
           /* generate the declaration part of the statement*/
           Object firstPart = generateQuad((SimpleNode)theNode.children[0]);

           /* the for statement will have to branch up to the next statement, so
              save the current location */
           LocationStorageStruct storage = new LocationStorageStruct();
           storage.location = quadList.size();

           /* generate the quad for the second part of the 'for' statement that
              is the branching condition*/
           Object secondPart = generateQuad((SimpleNode)theNode.children[1]);
           LocationStorageStruct storage2 = new LocationStorageStruct();

           /* branch if condition*/
           Quad quad = new Quad(TeaQuadGeneratorConstants.BRANCHLTEZ,secondPart, null, storage2);
           save(quad);

           /* generate the fourth quad next because that is the statement block*/
           Object fourthPart = generateQuad((SimpleNode)theNode.children[3]);

           /* next generate the third block because this is the for statement
              third part which happens that the end of the loop*/
           Object thirdPart = generateQuad((SimpleNode)theNode.children[2]);

           Quad quad2 = new Quad(TeaQuadGeneratorConstants.BRANCH, null, null, storage);
           save(quad2);

           storage2.location = quadList.size();

       }
       return null;
    }

    /**
     * Generate the quad for the do-while statement
     * @param theNode SimpleNode
     * @return Object
     */
    public Object generateDoWhileStatementQuad(SimpleNode theNode)
    {
        /* we need to save the storage so that we know where to branch to
           if the branch condition is true*/
        LocationStorageStruct storage = new LocationStorageStruct();
        storage.location = quadList.size();

        /* generate the quads for the statement block*/
        int whileStatementConditionLoc = 0;
        if(theNode.children.length == 2)
        {
            generateQuad((SimpleNode)theNode.children[0]);
            whileStatementConditionLoc = 1;
        }

        /* generates the statement block quad*/
        Object quadResult = generateQuad((SimpleNode)theNode.children[whileStatementConditionLoc]);

        /* create the branch greater than quad because we want to go to the top of
           the do-while statement if the condition is true*/
        Quad quad = new Quad(TeaQuadGeneratorConstants.BRANCHGTZ,quadResult,null,storage);
        save(quad);

        return null;
    }


    /**
     * Generates the while statement quad
     * @param theNode SimpleNode
     * @return Object
     */
    protected Object generateWhileStatementQuad(SimpleNode theNode)
    {
        /* create the storage location so we can save where to branch */
        LocationStorageStruct storage = new LocationStorageStruct();
        storage.location = quadList.size();

        /* */
        LocationStorageStruct storage2 = new LocationStorageStruct();

        /* generate the quad for the condition*/
        Object expressionQuad = generateQuad((SimpleNode)theNode.children[0]);

        /* create the branch quad for the while statement*/
        Quad quad = new Quad(TeaQuadGeneratorConstants.BRANCHLTEZ,expressionQuad,null,storage2);
        save(quad);

        /* for each statement within the while statement generate the quads */
        for(int i = 1; i < theNode.children.length; i++)
        {
            generateQuad((SimpleNode)theNode.children[i]);
        }

        /* generate the branch quad that goes to the top of the while statement */
        Quad quad2 = new Quad(TeaQuadGeneratorConstants.BRANCH,null,null,storage);
        save(quad2);
        storage2.location = quadList.size();

        return null;
    }

    /**
     * Generates quads for the statement block
     * @param theNode SimpleNode
     * @return Object
     */
    protected Object generateStatementBlockQuad(SimpleNode theNode)
    {

        for(int i = 0; theNode.children != null && i < theNode.children.length; i++)
        {
            generateQuad((SimpleNode)theNode.children[i]);
        }

        return null;
    }

    /**
     * Generates the quads for the if statement
     * @param theNode SimpleNode
     * @return Object
     */
    protected Object generateIfStatementQuad(SimpleNode theNode)
    {
        Object expressionQuad = null;

        /* generate the quads for an if-else statement */
        if(theNode.firstValue != null && theNode.firstValue.equals("if") ||
           theNode.secondValue != null && theNode.secondValue.equals("else"))
        {
            expressionQuad = generateIfElseStatementQuad(theNode);
        }
        else
        {
            /* generate the quads for the condition*/
            expressionQuad = generateQuad((SimpleNode) theNode.children[
                                                 0]);

            /* create the storage location and create the quad to branch out of the if statement*/
            LocationStorageStruct storage = new LocationStorageStruct();
            Quad quad = new Quad(TeaQuadGeneratorConstants.BRANCHLTEZ,expressionQuad,null,storage);

            /* save the quad*/
            save(quad);

            /* for each other child generate its quad*/
            for(int i = 1; i < theNode.children.length; i++)
            {
               generateQuad((SimpleNode)theNode.children[i]);
            }

            /* set the storage's location so we know where to branch to*/
            storage.location = quadList.size();
            }

        return expressionQuad;
    }

    /**
     * Generates the if/else quad
     * @param theNode SimpleNode
     * @return Object
     */
    public Object generateIfElseStatementQuad(SimpleNode theNode)
    {
        /* generate the expression quad */
         Object expressionQuad = generateQuad((SimpleNode) theNode.children[
                                              0]);

         /* create the storage location for the branch condition*/
         LocationStorageStruct storage = new LocationStorageStruct();
         Quad quad = new Quad(TeaQuadGeneratorConstants.BRANCHLTEZ,expressionQuad,null,storage);

         save(quad);

         int i = 1;
         for(i = 1; i < theNode.children.length-1; i++)
         {
            generateQuad((SimpleNode)theNode.children[i]);
         }
         /* create the branch quad to branch out of the if/else condition*/
         LocationStorageStruct storage2 = new LocationStorageStruct();
         Quad quad2 = new Quad(TeaQuadGeneratorConstants.BRANCH,null,null,storage2);
         save(quad2);


         storage.location = quadList.size();
         Object quad3 = generateQuad((SimpleNode)theNode.children[i]);
         storage2.location = quadList.size();

         return expressionQuad;
    }

    /**
     * generates an expression quad (which is many different types of operations)
     * @param theNode SimpleNode
     * @return Object
     */
    protected Object generateExpressionQuad(SimpleNode theNode)
    {
        Object temp = null;
        Quad quad =null;

        /* generate quad for operator that has two children*/
        if(theNode.children.length == 2)
        {

            quad = new Quad(TeaTreeConstants.jjtNodeName[theNode.id].toUpperCase(),
                            generateQuad((SimpleNode) theNode.children[0]),
                            generateQuad((SimpleNode) theNode.children[1]),
                            temp = this.getLatestTemp());
        }
        else
        {
            /* generate quads for unary operators*/
            quad = new Quad(TeaTreeConstants.jjtNodeName[theNode.id].toUpperCase(),
                            generateQuad((SimpleNode) theNode.children[0]),
                            null,
                            temp = this.getLatestTemp());
        }

        save(quad);
        return temp;
    }

    /**
     * generate quads for declarative statements
     * @param theNode SimpleNode
     * @return Object
     */
    public Object generateDeclarativeQuad(SimpleNode theNode)
    {
        Quad quad = null;

        for(int i = 0; i < theNode.children.length; i++)
        {
            SimpleNode childNode = (SimpleNode) theNode.children[i];
            quad = new Quad(TeaQuadGeneratorConstants.DECLARE, null, null,
                            childNode.firstValue);
            save(quad);
            generateQuad(childNode);
        }
        return null;
    }

    /**
     * creates the latest temp
     * @return Object
     */
    public Object getLatestTemp()
    {
        return TeaQuadGeneratorConstants.TEMP_CONSTANT + tempCounter++;
    }

    /**
     * Generate assigment quad
     * @param theNode SimpleNode
     * @return Object
     */
    public Object generateAssignmentQuad(SimpleNode theNode)
    {
        Quad quad = null;

        if(theNode.children != null && theNode.children.length > 0)
        {

        quad = new Quad(TeaTreeConstants.jjtNodeName[theNode.id].toUpperCase(),
                        generateQuad((SimpleNode)theNode.children[0]),
                        null,
                        theNode.firstValue);
        save(quad);
        }

        return theNode.firstValue;
    }

    /**
     *
     * @param quad Quad
     */
    public void save(Quad quad)
    {
        quadList.add(quad);
    }
}

/**
 * Class to encapsulate QuadList (just overwrites toString() method)
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2006</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */
class QuadList extends ArrayList
{
   public String toString()
   {
       String returnValue = "";
       for(int i = 0; i < this.size(); i++)
       {
           returnValue = returnValue +  i+ ":: " + this.get(i);
       }
       return returnValue;
   }
}

/**
 * Storage location class so that we can storage a location after we store
 * the actual object within the quad
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2006</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */
class LocationStorageStruct {
   int location = -1;

   public String toString() {
       return location + "";
    }
}
/**
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2006</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */
class Quad {
    public Object operator = null;
    public Object leftHand = null;
    public Object rightHand = null;
    public Object register = null;

    public Quad(Object operator, Object leftHand, Object rightHand,
                Object register) {
        setOperator(operator);
        setLeftHand(leftHand);
        setRightHand(rightHand);
        setRegister(register);
    }

    public Object getOperator() {
        return operator;
    }

    public Object getLeftHand() {
        return leftHand;
    }

    public Object getRightHand() {
        return rightHand;
    }

    public Object getRegister() {
        return register;
    }

    public void setOperator(Object value)

    {
        operator = value;
    }

    public void setLeftHand(Object value)

    {
        leftHand = value;
    }

    public void setRightHand(Object value)

    {
        rightHand = value;
    }

    public void setRegister(Object value) {
        register = value;
    }

   public String toString()
   {
       return getOperator() + " | " + getLeftHand() + " | " + getRightHand() + " | " + getRegister() + "\n";
   }
}
